package ast;

import java.util.ArrayList;
import java.util.Hashtable;

public class TypeCheckVisitor implements Visitor {

	private ArrayList<String> basicTypes = new ArrayList<String>();
	private SymbolTable rootTable, currentTable, privates, publics;

	public TypeCheckVisitor(SymbolTable root) {
		basicTypes.add("chrlist");
		basicTypes.add("int");
		basicTypes.add("double");
		basicTypes.add("bool");
		rootTable = currentTable = root;
	}

	public String visit(AddNode n) {
		if (n.left.accept(this).equals("int")
				&& n.right.accept(this).equals("int")) {
			return "int";
		} else if ((n.left.accept(this).equals("double") && n.right
				.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("double"))) {
			return "double";
		} else if (n.left.accept(this).equals("chrlist")
				&& n.right.accept(this).equals("chrlist")) {
			return "chrlist";
		}
		System.out.println("Semantic Type Error: cannot use '+' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(AndNode n) {
		if (n.left.accept(this).equals("bool")
				&& n.right.accept(this).equals("bool")) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '&&' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(AssignNode n) {
		if (n.var.accept(this).equals(n.left.accept(this))) {
			return "pass";
		}
		System.out.println("Semantic Type Error: invalid assignment");
		return "fail";
	}

	public String visit(BasicTypeDecNode n) {
		return "pass";
	}

	public String visit(BodyNode n) {
		if ((n.vardecs != null && n.vardecs.accept(this).equals("pass"))
				&& (n.left != null && n.left.accept(this).equals("pass"))) {
			return "pass";
		} else if (n.vardecs == null && n.left == null) {
			return "pass";
		}
		return "fail";
	}

	public String visit(BooleanNode n) {
		return "bool";
	}

	public String visit(CallVarExprNode n) {
		return (String) n.left.accept(this);
	}

	// no two 'callvars' can follow each other, inferred from cup.
	public String visit(CallVarNode n) {
		if (n.left == null) {
			for (StoreSymbol ss : currentTable.table.get(n.id)) {
				if (ss.getSymbolType().equals("method")) {
					MethodStoreSymbol ms = (MethodStoreSymbol) ss;
					if (paramTypesMatch((List) n.right, ms.params)) {
						return ms.rType;
					}
				}
			}
		} else {
			VarNode vn = (VarNode) n.left;
			ClassStoreSymbol css = (ClassStoreSymbol) rootTable.table
					.get(vn.id).get(0);
			for (StoreSymbol ss : css.publicSymbols.table.get(n.id)) {
				if (ss.getSymbolType().equals("method")) {
					MethodStoreSymbol ms = (MethodStoreSymbol) ss;
					if (paramTypesMatch((List) n.right, ms.params)) {
						return ms.rType;
					}
				}
			}
		}
		return "fail";
	}

	private boolean paramTypesMatch(List list,
			ArrayList<TypeDecStoreSymbol> tdss) {
		if (list.equals(tdss) && list.equals(null)) {
			return true;
		}
		int counter = 0;
		List tmpList = list;
		while (tmpList != null && tmpList.left != null && counter < tdss.size()) {
			TypeDecNode tdn = (TypeDecNode) tmpList.left;
			if (!tdn.type.equals(tdss.get(counter).type)) {
				return false;
			}
			counter++;
			tmpList = (List) tmpList.right;
			if (counter >= tdss.size() && !tmpList.right.equals(null)) {
				return false;
			}
		}
		return true;
	}

	public String visit(CallVarStmtNode n) {
		return "pass";
	}

	public String visit(CharListNode n) {
		return "chrlist";
	}

	public String visit(ClassDecNode n) {
		ClassStoreSymbol cs = (ClassStoreSymbol) rootTable.table.get(n.id).get(
				0);

		publics = cs.publicSymbols;
		privates = cs.privateSymbols;
		if (n.left != null) {
			if (n.left.accept(this).equals("pass")) {
				return "pass";
			}
		}
		return "fail";
	}

	public Object visit(ClassDefNode n) {
		if ((n.instList == null || n.instList.accept(this).equals("pass"))
				&& (n.left == null || n.left.accept(this).equals("pass"))) {
			return "pass";
		}
		return "fail";
	}

	public String visit(DivideNode n) {
		if (n.left.accept(this).equals("int")
				&& n.right.accept(this).equals("int")) {
			return "int";
		} else if ((n.left.accept(this).equals("double") && n.right
				.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("double"))) {
			return "double";
		}
		System.out.println("Semantic Type Error: cannot use '/' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(EqNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '==' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(ExpNode n) {
		if ((n.left.accept(this).equals("double") && n.right.accept(this)
				.equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("double"))) {
			return "double";
		} else if (n.left.accept(this).equals("int")
				&& n.right.accept(this).equals("int")) {
			return "int";
		}
		System.out.println("Semantic Type Error: cannot use '^' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(FormalparamNode n) {
		if (n.left.accept(this).equals("pass")
				&& (n.right == null || n.right.accept(this).equals("pass"))) {
			return "pass";
		}
		return "fail";
	}

	public String visit(GeNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '>' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(GeqNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '>=' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(IfNode n) {
		if (n.e.accept(this).equals("bool")
				&& n.left.accept(this).equals("pass")) {
			return "pass";
		}
		System.out.println("Semantic Type Error: if expression must be a bool");
		return "fail";
	}

	public String visit(InNode n) {
		if (n.left.accept(this).equals("chrlist")
				&& n.right.accept(this).equals("chrlist")) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use 'in' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(InstVarDecNode n) {
		return "pass";
	}

	public String visit(IntNumNode n) {
		return "int";
	}

	public String visit(LenNode n) {
		if (n.left.accept(this).equals("string")) {
			return "int";
		}
		System.out.println("Semantic Type Error: cannot use 'len' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(LeNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '<' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(LeqNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '<=' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(List n) {
		if (n.left.accept(this).equals("pass")
				&& (n.right == null || n.right.accept(this).equals("pass"))) {
			return "pass";
		}
		return "fail";
	}

	public String visit(MainProgramNode n) {
		for (SymbolTable st : rootTable.children) {
			if (st.name.equals("start")) {
				currentTable = st;
			}
		}
		if ((n.declist == null || n.declist.accept(this).equals("pass"))
				&& (n.left == null || n.left.accept(this).equals("pass"))) {
			return "pass";
		}
		return "fail";
	}

	/*TODO needs fixing */
	public String visit(MethodBodyNode n) {
		if ((n.vardeclist == null || n.vardeclist.accept(this).equals("pass"))
				&& (n.left == null || n.left.accept(this).equals("pass"))) {
			if (n.right == null) {
				return "void";
			} else if(n.right != null){
				return (String) n.right.accept(this);
			}
		}
		System.out.println("Semantic Type Error: return type not valid");
		return "fail";
	}

	/*TODO needs fixing */
	public String visit(MethodDecNode n) {
		if (((n.type == null && n.left.accept(this).equals("void")) || (n.left
				.accept(this).equals(n.type)))
				&& (n.params == null || n.params.accept(this).equals("pass"))) {
			return "pass";
		}
		if (n.left != null) {
			n.left.accept(this);
		}
		System.out
				.println("Semantic Type Error: return type doesn't match declared return type");
		return "fail";
	}

	public String visit(MinusNode n) {
		if (n.left.accept(this).equals("int")
				&& n.right.accept(this).equals("int")) {
			return "int";
		} else if ((n.left.accept(this).equals("double") && n.right
				.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("double"))) {
			return "double";
		}
		System.out
				.println("Semantic Type Error: return type doesn't match declared type");
		return "fail";
	}

	public String visit(NeqNode n) {
		if ((n.left.accept(this).equals(n.right.accept(this)) && basicTypes
				.contains(n.left.accept(this)))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '!=' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(NotinNode n) {
		if (n.left.accept(this).equals("string")
				&& n.right.accept(this).equals("string")) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use 'not in' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(NotNode n) {
		if (n.left.accept(this).equals("bool")) {
			return "bool";
		}
		System.out.println("Semantic Type Error: can only use '!' on bools");
		return "fail";
	}

	public String visit(ObjTypeDecNode n) {
		return n.type;
	}

	public String visit(OrNode n) {
		if (n.left.accept(this).equals("bool")
				&& n.right.accept(this).equals("bool")) {
			return "bool";
		}
		System.out.println("Semantic Type Error: cannot use '||' on bools");
		return "fail";
	}

	public String visit(ParamlistNode n) {
		if (n.left.accept(this).equals("pass")
				&& (n.right == null || n.right.accept(this).equals("pass"))) {
			return "pass";
		}
		return "fail";
	}

	public String visit(ParamNode n) {
		if (basicTypes.contains(n.type) || rootTable.table.containsKey(n.type)) {
			return "pass";
		}
		System.out.println("Semantic Type Error: undeclared type, " + n.type);
		return "fail";
	}

	public String visit(ParanExprNode n) {
		return (String) n.en.accept(this);
	}

	public String visit(RealNumNode n) {
		return "double";
	}

	public String visit(RepeatNode n) {
		if ((n.left == null || n.left.accept(this).equals("pass"))
				&& n.right.accept(this).equals("bool")) {
			return "pass";
		}
		System.out
				.println("Semantic Type Error: repeat clause arguement must be a bool");
		return "fail";
	}

	public String visit(ReturnStmtNode n) {
		if (n.left != null) {
			return (String) n.left.accept(this);
		}
		return "void";
	}

	public String visit(RootNode n) {
		if ((n.left.accept(this).equals("pass") || n.left == null)
				&& n.right.accept(this).equals("pass")) {
			return "pass";
		}
		return "fail";
	}

	public String visit(TimesNode n) {
		if (n.left.accept(this).equals("int")
				&& n.right.accept(this).equals("int")) {
			return "int";
		} else if ((n.left.accept(this).equals("double") && n.right
				.accept(this).equals("int"))
				|| (n.left.accept(this).equals("int") && n.right.accept(this)
						.equals("double"))
				|| (n.left.accept(this).equals("double") && n.right
						.accept(this).equals("double"))) {
			return "double";
		}
		System.out.println("Semantic Type Error: cannot use '*' on "
				+ n.left.accept(this) + " and " + n.right.accept(this));
		return "fail";
	}

	public String visit(VarExprNode n) {
		return (String) n.left.accept(this);
	}

	/* TODO needs major fixing; method local scope fail */
	public String visit(VarNode n) {
		if (currentTable.name.equals("start") || n.left != null) {
			try {
				VarNode vn = (VarNode) n.left;
				ClassStoreSymbol css = (ClassStoreSymbol) rootTable.table.get(
						vn.id).get(0);
				for (StoreSymbol ss : css.publicSymbols.table.get(n.id)) {
					if (ss.getSymbolType().equals("dec")) {
						TypeDecStoreSymbol ms = (TypeDecStoreSymbol) ss;
						return ms.type;
					}
				}
			} catch (NullPointerException npe) {
				String c = (String) n.left.accept(this);
				ClassStoreSymbol css = (ClassStoreSymbol) rootTable.table
						.get(c).get(0);
				for (StoreSymbol ss : css.publicSymbols.table.get(n.id)) {
					if (ss.getSymbolType().equals("dec")) {
						TypeDecStoreSymbol ms = (TypeDecStoreSymbol) ss;
						return ms.type;
					}
				}
			}
		}
		if (n.left == null && currentTable.name.equals("start")) {
			for (StoreSymbol ss : currentTable.table.get(n.id)) {
				if (ss.getSymbolType().equals("dec")) {
					TypeDecStoreSymbol ms = (TypeDecStoreSymbol) ss;
					return ms.type;
				}
			}
		}
		return "fail";
	}

	public String visit(TypeDecNode n) {
		return n.type;
	}

	public Object visit(ExprNode n) {
		return n.accept(this);
	}
}